package com.tp.utils;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import com.jfinal.core.Controller;
import com.jfinal.kit.StrKit;
import com.jfinal.render.Render;

/**
 * 重构图形验证码
 * 
 * @author TP
 *
 */
public class JCaptchaRender extends Render {

	public static String captchaName = "captcha";

	// 随机字符
	private String code = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
	// 随机字体
	private String[] families = { "nyala", "Arial", "Bell MT", "Credit valley", "Impact", "Verdana", "Tahoma",
			Font.MONOSPACED };

	// 字符数区间
	private int font_min_num = 4;
	private int font_max_num = 5;
	// 字体大小区间
	private int font_min_size = 26;
	private int font_max_size = 32;
	// 图片大小
	private int width = 105;
	private int height = 35;
	// 颜色区间
	private Color min = new Color(20, 40, 80);
	private Color max = new Color(21, 50, 140);

	private Color color;

	private static Random random = new Random();

	// 是否启用随机颜色
	private boolean randomColor = true;

	// 背景色
	private Color bgColor = new Color(255, 255, 255);

	// 验证码字符颜色
	private Color drawColor = new Color(0, 0, 0);

	// 噪点数量
	private int artifactNum = 40;

	// 干扰线条数
	private int lineNum = 2;

	// 颜色创建工厂,使用一定范围内的随机色
	public Color getColor() {
		if (color == null) {
			color = new Color(min.getRed() + random.nextInt((max.getRed() - min.getRed())),
					min.getGreen() + random.nextInt((max.getGreen() - min.getGreen())),
					min.getBlue() + random.nextInt((max.getBlue() - min.getBlue())));
		}
		return color;
	}

	// 随机字体生成器
	public Font getFont() {
		String family = String.valueOf(families[random.nextInt(families.length)]);
		boolean bold = random.nextBoolean();
		int size = font_min_size;
		if (font_max_size - font_min_size > 0) {
			size += random.nextInt(font_max_size - font_min_size);
		}
		return new Font(family, bold ? Font.BOLD : Font.PLAIN, size);
	}

	// 随机字符生成器,去除掉容易混淆的字母和数字,如o和0等
	public String getNextWord() {
		StringBuffer sb = new StringBuffer();
		int l = font_min_num + (font_max_num > font_min_num ? random.nextInt(font_max_num - font_min_num) : 0);
		for (int i = 0; i < l; i++) {
			int j = random.nextInt(code.length());
			sb.append(code.charAt(j));
		}
		return sb.toString();
	}

	public void fillBackground(BufferedImage image) {
		Graphics2D graphics = (Graphics2D) image.getGraphics();

		// 验证码图片的宽高
		int imgWidth = image.getWidth();
		int imgHeight = image.getHeight();

		if (bgColor != null) {
			for (int x = 0; x < imgWidth; x++) {
				for (int y = 0; y < imgHeight; y++) {
					image.setRGB(x, y, bgColor.getRGB());
				}
			}
			// 填充为白色背景
			graphics.setColor(bgColor);
			graphics.fillRect(0, 0, imgWidth, imgHeight);
		}
		if (drawColor == null)
			drawColor = getRandomColor();

		if (artifactNum > 0) {
			// 画i个噪点(颜色及位置随机)
			for (int i = 0; i < artifactNum; i++) {
				// 随机颜色
				graphics.setColor(drawColor);
				// 随机位置
				int xInt = random.nextInt(imgWidth - 3);
				int yInt = random.nextInt(imgHeight - 2);
				// 随机旋转角度
				int sAngleInt = random.nextInt(360);
				int eAngleInt = random.nextInt(360);
				// 随机大小
				int wInt = random.nextInt(6);
				int hInt = random.nextInt(6);
				graphics.fillArc(xInt, yInt, wInt, hInt, sAngleInt, eAngleInt);
			}
		}
		// 画干扰线
		if (lineNum > 0) {
			for (int i = 0; i < lineNum; i++) {
				// 随机位置
				int xInt = random.nextInt(imgWidth - 3);
				int yInt = random.nextInt(imgHeight - 2);
				int xInt2 = random.nextInt(imgWidth);
				int yInt2 = random.nextInt(imgHeight);
				graphics.drawLine(xInt, yInt, xInt2, yInt2);
			}
		}
		graphics.setRenderingHints(
				new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
		int cp = 4 + random.nextInt(3);
		int[] xPoints = new int[cp];
		int[] yPoints = new int[cp];
		imgWidth -= 10;
		for (int i = 0; i < cp; i++) {
			xPoints[i] = (int) ((int) 5 + (i * imgWidth) / (cp - 1));
			yPoints[i] = (int) (imgHeight * (random.nextDouble() * 0.5 + 0.2));
		}
		int subsections = 6;
		int[] xPointsSpline = new int[(cp - 1) * subsections];
		int[] yPointsSpline = new int[(cp - 1) * subsections];
		for (int i = 0; i < cp - 1; i++) {
			double x0 = i > 0 ? xPoints[i - 1] : 2 * xPoints[i] - xPoints[i + 1];
			double x1 = xPoints[i];
			double x2 = xPoints[i + 1];
			double x3 = (i + 2 < cp) ? xPoints[i + 2] : 2 * xPoints[i + 1] - xPoints[i];
			double y0 = i > 0 ? yPoints[i - 1] : 2 * yPoints[i] - yPoints[i + 1];
			double y1 = yPoints[i];
			double y2 = yPoints[i + 1];
			double y3 = (i + 2 < cp) ? yPoints[i + 2] : 2 * yPoints[i + 1] - yPoints[i];
			for (int j = 0; j < subsections; j++) {
				xPointsSpline[i * subsections + j] = (int) catmullRomSpline(x0, x1, x2, x3, 1.0 / subsections * j);
				yPointsSpline[i * subsections + j] = (int) catmullRomSpline(y0, y1, y2, y3, 1.0 / subsections * j);
			}
		}
		for (int i = 0; i < xPointsSpline.length - 1; i++) {
			graphics.setColor(drawColor);
			graphics.setStroke(new BasicStroke(0.2f + 2 * random.nextFloat()));
			graphics.drawLine(xPointsSpline[i], yPointsSpline[i], xPointsSpline[i + 1], yPointsSpline[i + 1]);
		}
	}

	private double hermiteSpline(double x1, double a1, double x2, double a2, double t) {
		double t2 = t * t;
		double t3 = t2 * t;
		double b = -a2 - 2.0 * a1 - 3.0 * x1 + 3.0 * x2;
		double a = a2 + a1 + 2.0 * x1 - 2.0 * x2;
		return a * t3 + b * t2 + a1 * t + x1;
	}

	private double catmullRomSpline(double x0, double x1, double x2, double x3, double t) {
		double a1 = (x2 - x0) / 2;
		double a2 = (x3 - x1) / 2;
		return hermiteSpline(x1, a1, x2, a2, t);
	}

	public Color getRandomColor() {
		int[] c = new int[3];
		int i = random.nextInt(c.length);
		for (int fi = 0; fi < c.length; fi++) {
			if (fi == i) {
				c[fi] = random.nextInt(71);
			} else {
				c[fi] = random.nextInt(256);
			}
		}
		return new Color(c[0], c[1], c[2]);
	}

	public void draw(String text, BufferedImage canvas) {
		Graphics2D g = (Graphics2D) canvas.getGraphics();

		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

		for (int i = 0; i < text.length(); i++) {
			String c = String.valueOf(text.charAt(i));
			// 设定字体
			g.setFont(getFont());
			// 旋转度数 最好小于45度
			int degree = random.nextInt(28);
			if (i % 2 == 0) {
				degree = degree * (-1);
			}
			// 定义坐标
			int x = 22 * i, y = 21;
			// 旋转区域
			g.rotate(Math.toRadians(degree), x, y);
			// 设定字体颜色
			g.setColor(getColor());
			// 将认证码显示到图象中
			g.drawString(c, x + 8, y + 10);
			// 旋转之后，必须旋转回来
			g.rotate(-Math.toRadians(degree), x, y);
		}
		// 销毁图像
		g.dispose();
	}

	public BufferedImage applyFilters(BufferedImage source) {
		BufferedImage dest = source;
		int x = (source.getWidth() - dest.getWidth()) / 2;
		int y = (source.getHeight() - dest.getHeight()) / 2;
		source = new BufferedImage(source.getWidth(), source.getHeight(), source.getType());
		source.getGraphics().drawImage(dest, x, y, null);
		return source;
	}

	@Override
	public void render() {
		if (randomColor) {
			drawColor = getColor();
		}
		// 得到验证码对象,有验证码图片和验证码字符串
		BufferedImage bufImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		fillBackground(bufImage);
		String word = getNextWord();
		draw(word, bufImage);
		bufImage = applyFilters(bufImage);
		
		System.out.println("****" + word + "****");
		String vCode = EncryptionUtil.encryptMD5ToString(word.toLowerCase());
		Cookie cookie = new Cookie(captchaName, vCode);
		cookie.setMaxAge(-1);
		cookie.setPath("/");
		response.addCookie(cookie);
		response.setHeader("Pragma", "no-cache");
		response.setHeader("Cache-Control", "no-cache");
		response.setDateHeader("Expires", 0);
		response.setContentType("image/jpeg");
		
		ServletOutputStream outputStream = null;
		try {
			outputStream = response.getOutputStream();
			ImageIO.write(bufImage, "png", outputStream);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (outputStream != null)
				try {
					outputStream.flush();
					outputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}

	public static boolean validate(Controller controller, String inputRandomCode) {
		if (StrKit.isBlank(inputRandomCode)) {
			return false;
		}
		try {
			String captchaCode = EncryptionUtil.encryptMD5ToString(inputRandomCode.toLowerCase());
			return captchaCode.equals(controller.getCookie(captchaName));
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}
}
